-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: collect styles in dev #6343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughAdds CSS Modules e2e projects for React, Solid, and Vue; implements a dev-mode CSS collector and middleware to serve route-scoped CSS from the Vite dev server; and updates framework HeadContent components to inject a dev-only stylesheet link that is removed after hydration. Includes Playwright e2e tests and TypeScript/Vite configs for each example. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser/Client
participant Middleware as Dev Middleware (/@@styles.css)
participant Vite as Vite Dev Server
participant Graph as Module Graph
participant Router as Router State / Route Manifest
Browser->>Middleware: GET /@tanstack-start/styles.css?routes=...
Middleware->>Router: Validate & resolve route IDs (manifest)
Middleware->>Vite: collectDevStyles(entries)
Vite->>Graph: Traverse entries -> ensure SSR transforms
Graph->>Graph: Extract CSS from transformed modules
Graph-->>Vite: Return per-file CSS content
Vite-->>Middleware: Aggregated CSS payload
Middleware-->>Browser: Respond text/css (no-store)
Browser->>Browser: HeadContent renders DevStylesLink (href to endpoint)
Browser->>Browser: After hydration remove SSR link to avoid duplication
Browser->>Middleware: On navigation, request styles for new routes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:eslint,test:unit,tes... |
❌ Failed | 1m 51s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 2s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-01-10 03:06:24 UTC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @e2e/vue-start/css-modules/package.json:
- Around line 16-20: Update the internal dependency versions in package.json
that use the workspace protocol from "workspace:^" to "workspace:*" —
specifically replace "@tanstack/vue-router", "@tanstack/vue-start", and
"@tanstack/router-e2e-utils" (and any other internal @tanstack/* entries on
lines 21-30) to use "workspace:*" so they follow the repo guideline for internal
workspace dependencies.
In @packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts:
- Around line 146-158: The loadCssContent function currently calls
vite.transformRequest(node.url) which can throw and break the entire styles
traversal; wrap that call in a try/catch inside loadCssContent so any thrown
error is caught, optionally log the error (or debug) and return undefined to
make CSS loading best-effort, and keep the existing check for
transformResult?.code before calling extractCssFromViteModule; ensure no
transformRequest exceptions bubble out of loadCssContent.
In @packages/start-plugin-core/src/dev-server-plugin/plugin.ts:
- Around line 79-124: The middleware should only match the exact styles route
and handle async errors: replace the loose startsWith check with an exact
pathname check (e.g., ensure new URL(req.url, base).pathname ===
'/@tanstack-start/styles.css' and optionally verify req.method === 'GET'), and
wrap the URL parsing + collectDevStyles call in a try/catch so exceptions don't
become unhandled; on error log via viteDevServer.config.logger.error (or
console.error) and respond with a 500 status and an empty body. Keep the
existing route manifest lookup (TSS_ROUTES_MANIFEST) and
entries/collectDevStyles usage, but move the new URL(...) and await
collectDevStyles(...) inside the try block and only call res.setHeader/res.end
in the normal path (use res.statusCode = 500; res.end('') in the catch).
🧹 Nitpick comments (12)
e2e/vue-start/css-modules/src/router.tsx (1)
4-12: Good SSR-friendly router factory; refactor suggestion to inline return available.
Factory pattern is appropriate for SSR isolation. BothscrollRestorationanddefaultPreloadare validcreateRouteroptions.Proposed refactor (no behavior change)
export function getRouter() { - const router = createRouter({ + return createRouter({ routeTree, scrollRestoration: true, defaultPreload: false, }) - - return router }e2e/solid-start/css-modules/tsconfig.json (1)
1-24: Solid e2e tsconfig looks solid; consider droppingallowJsunless you truly have JS sources.
strict: true,moduleResolution: "Bundler", andjsxImportSource: "solid-js"are aligned for Vite/Solid. If there are no.jsinputs,allowJs: trueis just extra surface area for less-typed code.e2e/react-start/css-modules/tests/css.spec.ts (1)
73-84: Consider using more deterministic waiting strategies instead of arbitrary timeouts.Lines 77, 90, and 104 use
page.waitForTimeout(1000)to wait for hydration. While this works, it can lead to flaky tests in slower CI environments or unnecessarily slow tests in fast environments.♻️ More reliable alternatives
Consider one of these approaches:
Option 1: Wait for network idle
- await page.waitForTimeout(1000) + await page.waitForLoadState('networkidle')Option 2: Wait for a hydration marker
Add a data attribute when hydration completes and wait for it:- await page.waitForTimeout(1000) + await page.waitForSelector('[data-hydrated="true"]')Option 3: Wait for interactive state
- await page.waitForTimeout(1000) + await page.waitForLoadState('domcontentloaded')Apply similar changes to lines 90 and 104.
e2e/solid-start/css-modules/tests/css.spec.ts (1)
77-77: Consider using deterministic waits instead of fixed timeouts.The test uses
waitForTimeout(1000)to wait for hydration. Fixed timeouts can make tests flaky and slower than necessary. Consider waiting for specific hydration indicators instead:♻️ More reliable hydration detection
- // Wait for hydration - await page.waitForTimeout(1000) + // Wait for hydration by checking for React's hydration marker or a specific event + await page.waitForLoadState('networkidle')Alternatively, if there's a specific hydration indicator in your app (e.g., a data attribute or class added post-hydration), wait for that:
await page.waitForSelector('[data-hydrated="true"]', { state: 'attached' })This makes tests more deterministic and potentially faster.
Also applies to: 90-90, 104-104
e2e/vue-start/css-modules/tests/css.spec.ts (1)
77-77: Consider using deterministic waits instead of fixed timeouts.Similar to the Solid test suite, this test uses
waitForTimeout(1000)for hydration waits. Deterministic waits (e.g.,waitForLoadState('networkidle')or waiting for specific hydration markers) would make the tests more reliable and potentially faster.Also applies to: 90-90, 104-104
packages/react-router/src/HeadContent.tsx (1)
206-237: Avoid removing a React-owned DOM node; make the component stop rendering after mount instead.
useEffect(...querySelectorAll(...).remove())deletes the same<link>React just hydrated/created, butDevStylesLink()keeps returning that<link>on every render. This can cause DOM/VDOM divergence and unpredictable updates during client navigations.Proposed fix (React-controlled removal)
function DevStylesLink() { const router = useRouter() + const [hydrated, setHydrated] = React.useState(false) const routeIds = useRouterState({ select: (state) => state.matches.map((match) => match.routeId), }) React.useEffect(() => { - // After hydration, remove the SSR-rendered dev styles link - document - .querySelectorAll('[data-tanstack-start-dev-styles]') - .forEach((el) => el.remove()) + setHydrated(true) }, []) + if (hydrated) return null + // Build the same href on both server and client for hydration match const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}` return ( <link rel="stylesheet" href={href} data-tanstack-start-dev-styles suppressHydrationWarning /> ) }packages/vue-router/src/HeadContent.tsx (1)
9-42: Avoid manual DOM removal; make DevStylesLink render nothing after mount.
onMounted(...remove())deletes the hydrated node while the component still renders it, risking Vue patching inconsistencies.Proposed fix (Vue-controlled removal)
const DevStylesLink = Vue.defineComponent({ name: 'DevStylesLink', setup() { + const hydrated = Vue.ref(false) const routeIds = useRouterState({ select: (state) => state.matches.map((match) => match.routeId), }) Vue.onMounted(() => { - // After hydration, remove the SSR-rendered dev styles link - document - .querySelectorAll('[data-tanstack-start-dev-styles]') - .forEach((el) => el.remove()) + hydrated.value = true }) // Build the same href on both server and client for hydration match const href = Vue.computed( () => `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.value.join(','))}`, ) return () => + hydrated.value + ? null + : Vue.h('link', { rel: 'stylesheet', href: href.value, 'data-tanstack-start-dev-styles': true, }) }, })packages/solid-router/src/HeadContent.tsx (1)
200-223: Avoid manual DOM removal; make DevStylesLink stop rendering after mount.As written, Solid hydrates/renders
<link ...data-tanstack-start-dev-styles />, thenonMountremoves it from the DOM while the component still returns it.Proposed fix (Solid-controlled removal)
function DevStylesLink() { const routeIds = useRouterState({ select: (state) => state.matches.map((match) => match.routeId), }) + const [hydrated, setHydrated] = Solid.createSignal(false) onMount(() => { - // After hydration, remove the SSR-rendered dev styles link - document - .querySelectorAll('[data-tanstack-start-dev-styles]') - .forEach((el) => el.remove()) + setHydrated(true) }) + if (hydrated()) return null + // Build the same href on both server and client for hydration match const href = () => `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds().join(','))}` return <link rel="stylesheet" href={href()} data-tanstack-start-dev-styles /> }Also applies to: 234-238
packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts (3)
76-100: Keyvisitedby URL (string), notModuleNodeidentity (and avoid reassigningnode).You
visited.add(node)and later potentially replacenodeviagetModuleByUrl. If Vite returns a differentModuleNodeinstance, the visited guard can be bypassed and traversal work can balloon.Proposed fix (URL-based visited + no param reassignment)
async function crawlModuleForCss( vite: ViteDevServer, node: ModuleNode, - visited: Set<ModuleNode>, + visited: Set<string>, styles: Map<string, string>, ): Promise<void> { - if (visited.has(node)) return - visited.add(node) + const nodeKey = node.url + if (visited.has(nodeKey)) return + visited.add(nodeKey) const branches: Array<Promise<void>> = [] // Ensure the module has been transformed to populate its deps // This is important for code-split modules that may not have been processed yet - if (!node.ssrTransformResult) { + let currentNode = node + if (!currentNode.ssrTransformResult) { try { - await vite.transformRequest(node.url, { ssr: true }) + await vite.transformRequest(currentNode.url, { ssr: true }) // Re-fetch the node to get updated state - const updatedNode = await vite.moduleGraph.getModuleByUrl(node.url) + const updatedNode = await vite.moduleGraph.getModuleByUrl(currentNode.url) if (updatedNode) { - node = updatedNode + currentNode = updatedNode } } catch { // Ignore transform errors - the module might not be transformable } } // Check if this is a CSS file if ( - node.file && - isCssFile(node.file) && - !hasCssSideEffectFreeParam(node.url) + currentNode.file && + isCssFile(currentNode.file) && + !hasCssSideEffectFreeParam(currentNode.url) ) { - const css = await loadCssContent(vite, node) + const css = await loadCssContent(vite, currentNode) if (css) { - styles.set(node.url, css) + styles.set(currentNode.url, css) } } const depsFromSsr = node.ssrTransformResult?.deps ?? [](Also update
collectDevStylestoconst visited = new Set<string>().)Also applies to: 82-96
17-28: Treat “side-effect-free” query params as present regardless of value.Right now
hasCssSideEffectFreeParamonly filters?raw/?urlwhen the param is valueless (and not?raw=). If user code uses?raw=true, this will still be collected/injected.Proposed fix
function hasCssSideEffectFreeParam(url: string): boolean { const queryString = url.split('?')[1] if (!queryString) return false const params = new URLSearchParams(queryString) - return CSS_SIDE_EFFECT_FREE_PARAMS.some( - (param) => - params.get(param) === '' && - !url.includes(`?${param}=`) && - !url.includes(`&${param}=`), - ) + return CSS_SIDE_EFFECT_FREE_PARAMS.some((param) => params.has(param)) }
160-175: CSS extraction via regex + manual unescape is brittle; prefer parsing the literal.This assumes Vite emits
const __vite__css = "...";and only unescapes a small subset of escape sequences. If Vite changes emitted syntax (e.g.,let, different naming, or uses\r,\uXXXX, etc.), dev CSS collection silently fails.
- Verify (for your Vite version) that transformed CSS modules always contain a
__vite__cssstring declared viaconst.- If yes, consider making extraction robust by parsing the JS and reading the literal value (AST), rather than regex + partial unescaping.
packages/start-plugin-core/src/dev-server-plugin/plugin.ts (1)
89-96: Consider bounding theroutesquery param size (dev-only but easy to footgun).A very large
routeslist can force expensive graph crawling per request.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (50)
e2e/react-start/css-modules/.gitignoree2e/react-start/css-modules/.prettierignoree2e/react-start/css-modules/package.jsone2e/react-start/css-modules/playwright.config.tse2e/react-start/css-modules/src/router.tsxe2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/index.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/react-start/css-modules/src/styles/card.module.csse2e/react-start/css-modules/src/styles/global.csse2e/react-start/css-modules/tests/css.spec.tse2e/react-start/css-modules/tests/setup/global.setup.tse2e/react-start/css-modules/tests/setup/global.teardown.tse2e/react-start/css-modules/tsconfig.jsone2e/react-start/css-modules/vite.config.tse2e/solid-start/css-modules/.gitignoree2e/solid-start/css-modules/.prettierignoree2e/solid-start/css-modules/package.jsone2e/solid-start/css-modules/playwright.config.tse2e/solid-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/routes/__root.tsxe2e/solid-start/css-modules/src/routes/index.tsxe2e/solid-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/styles/card.module.csse2e/solid-start/css-modules/src/styles/global.csse2e/solid-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/tests/setup/global.setup.tse2e/solid-start/css-modules/tests/setup/global.teardown.tse2e/solid-start/css-modules/tsconfig.jsone2e/solid-start/css-modules/vite.config.tse2e/vue-start/css-modules/.gitignoree2e/vue-start/css-modules/.prettierignoree2e/vue-start/css-modules/package.jsone2e/vue-start/css-modules/playwright.config.tse2e/vue-start/css-modules/src/router.tsxe2e/vue-start/css-modules/src/routes/__root.tsxe2e/vue-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/src/styles/card.module.csse2e/vue-start/css-modules/src/styles/global.csse2e/vue-start/css-modules/tests/css.spec.tse2e/vue-start/css-modules/tests/setup/global.setup.tse2e/vue-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/tsconfig.jsone2e/vue-start/css-modules/vite.config.tspackages/react-router/src/HeadContent.tsxpackages/solid-router/src/HeadContent.tsxpackages/start-plugin-core/src/dev-server-plugin/dev-styles.tspackages/start-plugin-core/src/dev-server-plugin/plugin.tspackages/vue-router/src/HeadContent.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety for all code
Files:
e2e/react-start/css-modules/tests/setup/global.teardown.tse2e/solid-start/css-modules/playwright.config.tse2e/solid-start/css-modules/vite.config.tse2e/vue-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/router.tsxe2e/react-start/css-modules/tests/setup/global.setup.tse2e/vue-start/css-modules/tests/css.spec.tse2e/react-start/css-modules/src/router.tsxe2e/solid-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/tests/setup/global.setup.tspackages/vue-router/src/HeadContent.tsxe2e/react-start/css-modules/tests/css.spec.tspackages/start-plugin-core/src/dev-server-plugin/plugin.tse2e/solid-start/css-modules/src/routes/modules.tsxe2e/react-start/css-modules/vite.config.tspackages/start-plugin-core/src/dev-server-plugin/dev-styles.tse2e/solid-start/css-modules/src/routes/__root.tsxe2e/vue-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/src/routes/index.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/routes/index.tsxpackages/solid-router/src/HeadContent.tsxe2e/solid-start/css-modules/tests/setup/global.setup.tse2e/vue-start/css-modules/playwright.config.tse2e/vue-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/playwright.config.tspackages/react-router/src/HeadContent.tsxe2e/react-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/vite.config.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Implement ESLint rules for router best practices using the ESLint plugin router
Files:
e2e/react-start/css-modules/tests/setup/global.teardown.tse2e/solid-start/css-modules/playwright.config.tse2e/solid-start/css-modules/vite.config.tse2e/vue-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/router.tsxe2e/react-start/css-modules/tests/setup/global.setup.tse2e/vue-start/css-modules/tests/css.spec.tse2e/react-start/css-modules/src/router.tsxe2e/solid-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/tests/setup/global.setup.tspackages/vue-router/src/HeadContent.tsxe2e/react-start/css-modules/tests/css.spec.tspackages/start-plugin-core/src/dev-server-plugin/plugin.tse2e/solid-start/css-modules/src/routes/modules.tsxe2e/react-start/css-modules/vite.config.tspackages/start-plugin-core/src/dev-server-plugin/dev-styles.tse2e/solid-start/css-modules/src/routes/__root.tsxe2e/vue-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/src/routes/index.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/routes/index.tsxpackages/solid-router/src/HeadContent.tsxe2e/solid-start/css-modules/tests/setup/global.setup.tse2e/vue-start/css-modules/playwright.config.tse2e/vue-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/playwright.config.tspackages/react-router/src/HeadContent.tsxe2e/react-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/vite.config.ts
**/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Use workspace protocol
workspace:*for internal dependencies in package.json files
Files:
e2e/react-start/css-modules/package.jsone2e/vue-start/css-modules/package.jsone2e/solid-start/css-modules/package.json
🧠 Learnings (13)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.
Applied to files:
e2e/react-start/css-modules/.prettierignoree2e/react-start/css-modules/.gitignoree2e/vue-start/css-modules/src/router.tsxe2e/vue-start/css-modules/.gitignoree2e/react-start/css-modules/src/router.tsxe2e/vue-start/css-modules/.prettierignoree2e/solid-start/css-modules/.prettierignoree2e/solid-start/css-modules/.gitignoree2e/vue-start/css-modules/src/routes/index.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/react-start/css-modules/src/routes/__root.tsx
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
Applied to files:
e2e/react-start/css-modules/.prettierignoree2e/react-start/css-modules/.gitignoree2e/vue-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/router.tsxe2e/vue-start/css-modules/.gitignoree2e/react-start/css-modules/src/router.tsxe2e/react-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/.prettierignoree2e/solid-start/css-modules/.prettierignoree2e/vue-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/.gitignoree2e/vue-start/css-modules/src/routes/index.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.
Applied to files:
e2e/react-start/css-modules/.prettierignoree2e/react-start/css-modules/tests/setup/global.setup.tse2e/react-start/css-modules/package.jsone2e/react-start/css-modules/tsconfig.jsone2e/vue-start/css-modules/tests/setup/global.setup.tspackages/start-plugin-core/src/dev-server-plugin/plugin.tse2e/vue-start/css-modules/.prettierignoree2e/solid-start/css-modules/src/routes/__root.tsxe2e/solid-start/css-modules/tsconfig.jsone2e/solid-start/css-modules/.prettierignoree2e/vue-start/css-modules/package.jsone2e/vue-start/css-modules/tsconfig.jsone2e/vue-start/css-modules/src/routes/index.tsxe2e/solid-start/css-modules/package.jsone2e/solid-start/css-modules/src/routes/index.tsxe2e/solid-start/css-modules/tests/setup/global.setup.tse2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/vite.config.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router
Applied to files:
e2e/react-start/css-modules/.prettierignoree2e/vue-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/router.tsxe2e/react-start/css-modules/tsconfig.jsone2e/react-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/.prettierignoree2e/solid-start/css-modules/.prettierignoree2e/vue-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/src/routes/index.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.
Applied to files:
e2e/react-start/css-modules/.prettierignoree2e/solid-start/css-modules/src/router.tsxe2e/react-start/css-modules/src/router.tsxe2e/solid-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/.prettierignoree2e/solid-start/css-modules/src/routes/__root.tsxe2e/solid-start/css-modules/.prettierignoree2e/vue-start/css-modules/src/routes/index.tsxe2e/solid-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.
Applied to files:
e2e/react-start/css-modules/.prettierignoree2e/vue-start/css-modules/.prettierignoree2e/solid-start/css-modules/.prettierignoree2e/vue-start/css-modules/src/routes/index.tsxe2e/solid-start/css-modules/src/routes/index.tsxe2e/vue-start/css-modules/src/routes/__root.tsxe2e/react-start/css-modules/src/routes/__root.tsx
📚 Learning: 2025-12-25T13:04:55.492Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
Applied to files:
e2e/react-start/css-modules/tests/setup/global.teardown.tse2e/solid-start/css-modules/playwright.config.tse2e/react-start/css-modules/tests/setup/global.setup.tse2e/react-start/css-modules/package.jsone2e/vue-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/tests/setup/global.setup.tse2e/react-start/css-modules/tests/css.spec.tse2e/vue-start/css-modules/tests/setup/global.teardown.tse2e/vue-start/css-modules/package.jsone2e/solid-start/css-modules/package.jsone2e/solid-start/css-modules/tests/setup/global.setup.tse2e/vue-start/css-modules/playwright.config.tse2e/react-start/css-modules/playwright.config.ts
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: In e2e test fixtures (files under e2e directories), empty or placeholder values in configuration files like site.webmanifest are acceptable and should not be flagged unless the test specifically validates those fields.
Applied to files:
e2e/solid-start/css-modules/playwright.config.tse2e/vue-start/css-modules/playwright.config.tse2e/react-start/css-modules/playwright.config.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with extensive type safety for all code
Applied to files:
e2e/react-start/css-modules/tsconfig.jsone2e/solid-start/css-modules/tsconfig.jsone2e/vue-start/css-modules/tsconfig.json
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.
Applied to files:
e2e/vue-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/tests/css.spec.tse2e/react-start/css-modules/tests/css.spec.tse2e/solid-start/css-modules/.prettierignoree2e/solid-start/css-modules/.gitignore
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions
Applied to files:
e2e/solid-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/src/routes/modules.tsxe2e/react-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/routes/index.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Separate framework-agnostic core logic from React/Solid bindings
Applied to files:
packages/solid-router/src/HeadContent.tsx
🧬 Code graph analysis (21)
e2e/react-start/css-modules/tests/setup/global.teardown.ts (2)
e2e/solid-start/css-modules/tests/setup/global.teardown.ts (1)
teardown(4-6)e2e/vue-start/css-modules/tests/setup/global.teardown.ts (1)
teardown(4-6)
e2e/vue-start/css-modules/src/router.tsx (2)
e2e/react-start/css-modules/src/router.tsx (1)
getRouter(4-12)e2e/solid-start/css-modules/src/router.tsx (1)
getRouter(4-12)
e2e/solid-start/css-modules/src/router.tsx (2)
e2e/react-start/css-modules/src/router.tsx (1)
getRouter(4-12)e2e/vue-start/css-modules/src/router.tsx (1)
getRouter(4-12)
e2e/react-start/css-modules/tests/setup/global.setup.ts (3)
e2e/solid-start/css-modules/tests/setup/global.setup.ts (1)
setup(4-6)e2e/vue-start/css-modules/tests/setup/global.setup.ts (1)
setup(4-6)packages/vue-router/src/HeadContent.tsx (2)
setup(17-41)setup(186-204)
e2e/react-start/css-modules/src/router.tsx (2)
e2e/solid-start/css-modules/src/router.tsx (1)
getRouter(4-12)e2e/vue-start/css-modules/src/router.tsx (1)
getRouter(4-12)
e2e/solid-start/css-modules/tests/setup/global.teardown.ts (2)
e2e/react-start/css-modules/tests/setup/global.teardown.ts (1)
teardown(4-6)e2e/vue-start/css-modules/tests/setup/global.teardown.ts (1)
teardown(4-6)
e2e/vue-start/css-modules/tests/setup/global.setup.ts (2)
e2e/react-start/css-modules/tests/setup/global.setup.ts (1)
setup(4-6)e2e/solid-start/css-modules/tests/setup/global.setup.ts (1)
setup(4-6)
packages/vue-router/src/HeadContent.tsx (1)
packages/router-core/src/router.ts (1)
state(1104-1106)
packages/start-plugin-core/src/dev-server-plugin/plugin.ts (3)
packages/start-plugin-core/src/types.ts (1)
GetConfigFn(29-33)packages/virtual-file-routes/src/api.ts (1)
route(66-84)packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts (1)
collectDevStyles(38-74)
e2e/solid-start/css-modules/src/routes/modules.tsx (3)
e2e/react-start/css-modules/src/routes/modules.tsx (1)
Route(4-6)e2e/solid-start/css-modules/src/routes/__root.tsx (1)
Route(10-18)e2e/vue-start/css-modules/src/routes/modules.tsx (1)
Route(4-6)
e2e/solid-start/css-modules/src/routes/__root.tsx (2)
e2e/react-start/css-modules/src/routes/__root.tsx (1)
Route(9-17)packages/solid-router/src/HeadContent.tsx (1)
HeadContent(231-240)
e2e/vue-start/css-modules/src/routes/modules.tsx (3)
e2e/react-start/css-modules/src/routes/modules.tsx (1)
Route(4-6)e2e/solid-start/css-modules/src/routes/modules.tsx (1)
Route(4-6)e2e/vue-start/css-modules/src/routes/index.tsx (1)
Route(4-6)
e2e/vue-start/css-modules/tests/setup/global.teardown.ts (2)
e2e/react-start/css-modules/tests/setup/global.teardown.ts (1)
teardown(4-6)e2e/solid-start/css-modules/tests/setup/global.teardown.ts (1)
teardown(4-6)
e2e/react-start/css-modules/src/routes/modules.tsx (2)
e2e/solid-start/css-modules/src/routes/modules.tsx (1)
Route(4-6)e2e/vue-start/css-modules/src/routes/modules.tsx (1)
Route(4-6)
e2e/solid-start/css-modules/src/routes/index.tsx (2)
e2e/react-start/css-modules/src/routes/index.tsx (1)
Route(4-6)e2e/vue-start/css-modules/src/routes/index.tsx (1)
Route(4-6)
packages/solid-router/src/HeadContent.tsx (1)
packages/router-core/src/router.ts (1)
state(1104-1106)
e2e/solid-start/css-modules/tests/setup/global.setup.ts (2)
e2e/react-start/css-modules/tests/setup/global.setup.ts (1)
setup(4-6)e2e/vue-start/css-modules/tests/setup/global.setup.ts (1)
setup(4-6)
e2e/vue-start/css-modules/playwright.config.ts (1)
examples/solid/quickstart-esbuild-file-based/build.js (1)
isDev(6-6)
e2e/vue-start/css-modules/src/routes/__root.tsx (1)
packages/vue-router/src/HeadContent.tsx (1)
HeadContent(184-205)
e2e/react-start/css-modules/playwright.config.ts (1)
examples/solid/quickstart-esbuild-file-based/build.js (1)
isDev(6-6)
packages/react-router/src/HeadContent.tsx (3)
packages/react-router/src/useRouter.tsx (1)
useRouter(16-25)packages/react-router/src/useRouterState.tsx (1)
useRouterState(43-71)packages/react-router/src/Asset.tsx (1)
Asset(11-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Preview
- GitHub Check: Test
🔇 Additional comments (53)
e2e/vue-start/css-modules/tsconfig.json (2)
3-23: LGTM! Compiler options are well-configured.The compiler options are correctly set up for a Vue 3 + Vite project with:
- Strict mode enabled (aligns with TypeScript best practices)
- Proper Vue 3 JSX configuration (
jsx: preserve,jsxImportSource: vue)- Vite-compatible settings (
isolatedModules,noEmit,moduleResolution: Bundler)- Modern ES targets and module system
- Appropriate path alias configuration
2-2: No changes needed to theincludepattern. This project uses a JSX/TSX approach with Vue 3 rather than Single File Components (.vue), so the current pattern["**/*.ts", "**/*.tsx"]correctly covers all the TypeScript files that need type checking.Likely an incorrect or invalid review comment.
e2e/vue-start/css-modules/src/router.tsx (1)
1-2: Imports look correct and minimal.
Nothing suspicious here; keepingrouteTreesourced from the generated file is consistent with the e2e setup.e2e/react-start/css-modules/.prettierignore (1)
1-1: LGTM!Correctly excludes the autogenerated route tree from Prettier formatting to prevent unnecessary churn.
e2e/solid-start/css-modules/.gitignore (1)
1-7: LGTM!Comprehensive and appropriate ignore patterns for an e2e test project. Properly excludes autogenerated files, build artifacts, and test outputs. Based on learnings, the port tracking pattern aligns with Unix-specific e2e test coordination needs.
e2e/vue-start/css-modules/.gitignore (1)
1-7: LGTM!Mirrors the solid-start configuration appropriately, maintaining consistency across e2e CSS Modules projects. All patterns are correct and necessary.
e2e/react-start/css-modules/.gitignore (1)
1-7: LGTM! Appropriate ignore patterns for e2e test project.All entries properly exclude build artifacts, generated files, and test results that should not be committed to version control.
e2e/vue-start/css-modules/.prettierignore (1)
1-1: LGTM! Correctly excludes autogenerated route tree from formatting.This follows the established pattern across other CSS Modules e2e projects in the repository.
e2e/react-start/css-modules/src/router.tsx (1)
1-12: LGTM! Standard router configuration for e2e testing.The implementation correctly follows the established pattern across all framework variants (React, Solid, Vue) with appropriate framework-specific imports and consistent router configuration.
e2e/solid-start/css-modules/.prettierignore (1)
1-1: LGTM! Correctly excludes autogenerated route tree from formatting.Consistent with the pattern established across all CSS Modules e2e projects.
e2e/solid-start/css-modules/src/router.tsx (1)
1-12: LGTM! Standard router configuration for Solid e2e testing.The implementation correctly uses the Solid-specific router import while maintaining consistency with the React and Vue versions. Router configuration is appropriate for e2e testing purposes.
e2e/solid-start/css-modules/vite.config.ts (1)
1-17: LGTM! Clean e2e test configuration.The Vite configuration is well-structured for testing CSS Modules with Solid Start SSR. The plugin ordering is correct (tsConfigPaths → tanstackStart → viteSolid), and SSR is explicitly enabled.
e2e/vue-start/css-modules/vite.config.ts (1)
1-17: LGTM! Consistent e2e test configuration.The Vue Vite configuration follows the same clean structure as the Solid config, with appropriate Vue-specific plugins (vueJsx). Plugin ordering is correct.
e2e/solid-start/css-modules/package.json (1)
1-31: LGTM! Follows workspace and e2e conventions.The package.json correctly uses the
workspace:^protocol for internal dependencies and follows the established e2e test script patterns. Unix-specific commands in scripts are acceptable for CI environments.Based on learnings, e2e test scripts are designed for CI Unix environments.
e2e/react-start/css-modules/tsconfig.json (1)
1-22: LGTM! Proper strict TypeScript configuration.The TypeScript configuration correctly enables strict mode and uses appropriate compiler options for a React + Vite e2e test project. The
jsx: "react-jsx"setting is correct for React, and path mappings align with the vite-tsconfig-paths plugin.As per coding guidelines, TypeScript strict mode is enabled.
e2e/solid-start/css-modules/src/routes/__root.tsx (2)
10-18: LGTM! Proper root route definition.The root route is correctly structured with appropriate head metadata and component assignment for Solid Start.
20-62: LGTM! Well-structured root component for CSS Modules testing.The RootComponent correctly implements the full HTML document structure with:
- Proper placement of HydrationScript and HeadContent (which includes DevStylesLink in dev mode)
- Test IDs for e2e verification (main-nav, nav-home, nav-modules)
- Correct Outlet placement for nested routes
- Scripts at the end of the body
This structure aligns with the PR's objective to fix FOUC by ensuring CSS is properly injected during SSR.
e2e/vue-start/css-modules/package.json (1)
4-15:sideEffects: falsemay be risky for CSS-based e2e fixtures—please confirm it can’t drop CSS.Given this project exists specifically to validate CSS (global + modules) behavior, please confirm no bundler/test path (now or future) relies on
sideEffectsin a way that could tree-shake CSS imports.
Based on learnings, the Unix-specific script syntax (rm -rf,$PORT, inline env var) is acceptable for these e2e projects.e2e/vue-start/css-modules/src/styles/global.css (1)
1-19: LGTM for e2e global styles (simple, deterministic selectors).e2e/react-start/css-modules/src/styles/card.module.css (1)
1-20: LGTM for CSS Module fixture; properties are clear for SSR/hydration assertions.e2e/react-start/css-modules/tests/setup/global.setup.ts (1)
1-6: No changes needed. JSON import attributes (with { type: 'json' }) are reliably supported throughout this repository's e2e test infrastructure and are used consistently across 150+ files in global setup files, teardown files, and Playwright configs. The tsconfig configuration (ES2020 target with Bundler module resolution) and widespread successful usage across multiple e2e frameworks (React, Vue, Solid) confirm this syntax works in the CI environment.Likely an incorrect or invalid review comment.
e2e/vue-start/css-modules/src/styles/card.module.css (1)
1-20: LGTM! Clean CSS Module for e2e testing.The CSS Module is well-structured with semantic color comments and appropriate styling for testing CSS Module behavior in development mode.
e2e/react-start/css-modules/src/styles/global.css (1)
1-19: LGTM! Clean global CSS for e2e testing.The global CSS file is well-structured and serves its purpose for testing global CSS collection in development mode.
e2e/vue-start/css-modules/tests/setup/global.teardown.ts (1)
1-6: LGTM! Consistent with other framework teardown implementations.The teardown function correctly follows the established pattern and properly stops the dummy server using the package name.
e2e/solid-start/css-modules/tests/setup/global.setup.ts (1)
1-6: LGTM! Consistent setup pattern across frameworks.The setup function correctly starts the dummy server using the package name and follows the same pattern used in React and Vue e2e projects.
e2e/react-start/css-modules/tests/setup/global.teardown.ts (1)
1-6: LGTM! Consistent teardown pattern.The teardown logic correctly stops the dummy server and follows the same pattern used across all framework e2e projects. The project's TypeScript version (5.9.0) and Node.js version (22.10.2) both support the JSON import attributes syntax (
with { type: 'json' }), and the tsconfig.json is properly configured withmodule: "ESNext",moduleResolution: "Bundler", andresolveJsonModule: true.e2e/vue-start/css-modules/tests/setup/global.setup.ts (2)
4-6: Setup flow matches the other e2e suites.
Starting the dummy server keyed bypackageJson.nameis consistent with the react/solid setups.
1-2: Remove this review comment—no changes needed.The code uses the standard and modern
with { type: 'json' }syntax, which is fully supported in Node.js 24.8.0 and TypeScript with ESNext target. This pattern is consistently used across all playwright.config.ts files in the e2e directory (e.g., react-router examples). The proposed change toassert { type: 'json' }would move to the older, deprecated syntax—the opposite direction from modern best practices. Playwright global setup handles this syntax without issue.Likely an incorrect or invalid review comment.
e2e/solid-start/css-modules/src/styles/global.css (1)
1-19: Nice: deterministic, easy-to-assert global styles for SSR/dev collection tests.
Class names are clear and the properties should produce stable computed styles.e2e/solid-start/css-modules/tests/setup/global.teardown.ts (2)
4-6: Teardown is clean and symmetric with setup.
1-2: No changes needed. Thewith { type: 'json' }syntax is the correct and stable form for Node.js v23.1.0 onward, and your project requires Node.js 24.8.0. The olderassert { type: 'json' }form was the experimental syntax that has been superseded. This import statement is consistent with 50+ other e2e test files across the codebase and will execute without issues.e2e/solid-start/css-modules/src/styles/card.module.css (1)
1-20: Looks good for CSS Modules coverage.
Simple selectors and stable properties make e2e assertions reliable.e2e/solid-start/css-modules/src/routes/index.tsx (2)
4-25: Route + markup are test-friendly and Solid-correct (class,data-testid).
This should exercise “CSS present even with JS disabled” as intended for the FOUC regression.
1-2: No issue found:~alias is properly configured.The
~alias is already defined intsconfig.jsonas"~/*": ["./src/*"], and thevite-tsconfig-pathsplugin invite.config.tsis configured to resolve these paths. The importimport '~/styles/global.css'will correctly resolve to./src/styles/global.csswithout compilation failure.Likely an incorrect or invalid review comment.
e2e/vue-start/css-modules/src/routes/modules.tsx (1)
1-27: LGTM! Vue route with CSS Modules correctly implemented.The route module correctly uses Vue-specific JSX syntax (
classattribute), follows TanStack file-based routing patterns, and properly imports/applies CSS Module classes for SSR testing.e2e/solid-start/css-modules/src/routes/modules.tsx (1)
1-27: LGTM! Solid route with CSS Modules correctly implemented.The route module correctly uses Solid-specific JSX syntax (
classattribute), follows TanStack file-based routing patterns, and maintains consistency with the Vue implementation.e2e/react-start/css-modules/src/routes/index.tsx (1)
1-25: LGTM! React route with global CSS correctly implemented.The route module correctly uses React-specific JSX syntax (
classNameattribute), follows TanStack file-based routing patterns, and properly imports global CSS for SSR testing.e2e/vue-start/css-modules/src/routes/index.tsx (1)
1-25: LGTM! Vue route with global CSS correctly implemented.The route module correctly uses Vue-specific JSX syntax (
classattribute), follows TanStack file-based routing patterns, and maintains consistency with the React implementation while using framework-appropriate syntax.e2e/react-start/css-modules/vite.config.ts (1)
10-16: The current plugin order is correct and aligns with all other TanStack Start examples.The
tanstackStart()plugin is intentionally placed beforeviteReact()across all 16+ react-start vite.config files in the codebase. The plugin implementation usesenforce: 'pre'to ensure it runs in the pre-processing phase before normal plugins, which is necessary for its configuration to apply correctly during JSX transformation.Likely an incorrect or invalid review comment.
e2e/solid-start/css-modules/playwright.config.ts (2)
9-40: LGTM! Playwright configuration is well-structured.The configuration properly:
- Sets up dynamic port allocation via
getTestServerPort- Configures environment variables for the test server
- Uses appropriate conditional logic for dev vs. production mode
- Follows the established pattern from other framework e2e configs in this PR
3-6: Module configuration is correctly set for import attributes and top-level await.The package is configured as an ES module (
"type": "module"in package.json) with TypeScript module set toESNext, which supports both the import attributes syntax and top-level await on lines 3 and 6.e2e/react-start/css-modules/src/routes/__root.tsx (1)
1-60: LGTM! Root route structure is correct for React 19 SSR.The implementation properly:
- Uses React 19's document rendering capabilities (returning
<html>directly)- Includes
HeadContentwhich will inject dev styles to prevent FOUC (per PR objective)- Provides navigation with test IDs for e2e verification
- Renders
Outletfor nested routes andScriptsfor hydrationThe structure aligns with the PR's goal of collecting and serving CSS during SSR to address issue #3023.
e2e/react-start/css-modules/tests/css.spec.ts (1)
1-137: Test coverage is comprehensive and addresses the PR objectives well.The test suite effectively validates:
- ✅ Global CSS and CSS Modules work on initial SSR load (with JS disabled)
- ✅ CSS Modules generate scoped/hashed class names
- ✅ Global CSS class names remain unscoped
- ✅ Styles persist after hydration (preventing FOUC)
- ✅ Styles work correctly during client-side navigation
This directly addresses issue #3023's requirement for CSS Modules support without flash of unstyled content.
e2e/vue-start/css-modules/src/routes/__root.tsx (1)
1-62: LGTM! Vue root route structure is correct and consistent with React implementation.The implementation:
- Properly uses Vue Router's
HtmlandBodycomponents for SSR- Includes
HeadContentwhich will injectDevStylesLinkin development (per snippet frompackages/vue-router/src/HeadContent.tsx)- Maintains structural consistency with the React version while using framework-appropriate APIs
- Provides the same navigation and test IDs for consistent e2e testing across frameworks
This ensures CSS Modules work correctly in Vue Start applications, achieving cross-framework feature parity.
e2e/react-start/css-modules/src/routes/modules.tsx (2)
8-27: LGTM! CSS Modules usage is correct and testable.The component:
- Properly imports and applies CSS module classes via the
stylesobject- Includes appropriate test IDs (
module-card,module-title,module-content) that match the expectations intests/css.spec.ts- Clearly documents the purpose (testing CSS Modules in SSR)
- Uses scoped class names that will be validated by the e2e tests
2-2: The~path alias is correctly configured. Thetsconfig.jsondefines"paths": { "~/*": ["./src/*"] }and thevite.config.tsuses thevite-tsconfig-pathsplugin, which automatically resolves the tsconfig paths at runtime. The import statement resolves correctly to the styles directory.e2e/react-start/css-modules/package.json (1)
17-18: LGTM! Correct workspace protocol usage.The workspace:^ protocol is correctly used for all internal TanStack dependencies, aligning with the coding guidelines for this repository.
Also applies to: 24-24
e2e/react-start/css-modules/playwright.config.ts (1)
1-40: LGTM! Well-structured Playwright configuration.The configuration correctly uses:
- Modern import attributes syntax for JSON imports
- Top-level await for async port resolution
- Environment-aware command switching between dev and production modes
- Proper test isolation with single worker
e2e/vue-start/css-modules/playwright.config.ts (1)
1-40: LGTM! Consistent configuration across frameworks.The Vue Playwright configuration follows the same well-structured pattern as the React and Solid versions, ensuring consistent test execution across all three framework implementations.
packages/react-router/src/HeadContent.tsx (1)
226-233: Consider basepath-awareness for the dev CSS endpoint href.Hard-coding
hrefto"/@tanstack-start/styles.css?...“may fail when the app is served from a non-root base path.
- Does TanStack Start expose a basepath (router option) that should prefix this URL in dev?
Also applies to: 248-253
packages/vue-router/src/HeadContent.tsx (1)
189-203: Dev-only prepend ordering looks good. Keeps the dev link ahead of route-managed tags.packages/solid-router/src/HeadContent.tsx (1)
1-4: Verify whether duplicate imports fromsolid-jsare allowed by lint rules.This file imports both
import * as Solid from 'solid-js'andimport { For, onMount } from 'solid-js'. Ifimport/no-duplicatesis enabled, CI may fail.packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts (1)
89-97: VerifytransformRequest(..., { ssr: true })is a supported Vite API in this repo’s Vite version.If unsupported,
node.ssrTransformResultmay never populate and dependency crawling will miss modules.
| "dependencies": { | ||
| "@tanstack/vue-router": "workspace:^", | ||
| "@tanstack/vue-start": "workspace:^", | ||
| "vue": "^3.5.16" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use workspace:* for internal dependencies (per repo guidelines).
@tanstack/vue-router, @tanstack/vue-start, and @tanstack/router-e2e-utils should use workspace:* instead of workspace:^ to match the repo rule for **/package.json.
Proposed diff
"dependencies": {
- "@tanstack/vue-router": "workspace:^",
- "@tanstack/vue-start": "workspace:^",
+ "@tanstack/vue-router": "workspace:*",
+ "@tanstack/vue-start": "workspace:*",
"vue": "^3.5.16"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
- "@tanstack/router-e2e-utils": "workspace:^",
+ "@tanstack/router-e2e-utils": "workspace:*",
"@types/node": "^22.10.2",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"srvx": "^0.10.0",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}Also applies to: 21-30
🤖 Prompt for AI Agents
In @e2e/vue-start/css-modules/package.json around lines 16 - 20, Update the
internal dependency versions in package.json that use the workspace protocol
from "workspace:^" to "workspace:*" — specifically replace
"@tanstack/vue-router", "@tanstack/vue-start", and "@tanstack/router-e2e-utils"
(and any other internal @tanstack/* entries on lines 21-30) to use "workspace:*"
so they follow the repo guideline for internal workspace dependencies.
| async function loadCssContent( | ||
| vite: ViteDevServer, | ||
| node: ModuleNode, | ||
| ): Promise<string | undefined> { | ||
| // For ALL CSS files (including CSS modules), get the transformed content | ||
| // and extract __vite__css. Vite's transform puts the final CSS (with hashed | ||
| // class names for modules) into the __vite__css variable. | ||
| const transformResult = await vite.transformRequest(node.url) | ||
| if (!transformResult?.code) return undefined | ||
|
|
||
| // Extract CSS content from Vite's transformed module | ||
| return extractCssFromViteModule(transformResult.code) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make CSS loading best-effort (don’t let one transform failure break the whole response).
vite.transformRequest(node.url) can throw; right now that would reject traversal and likely break the /styles.css request entirely.
Proposed fix
async function loadCssContent(
vite: ViteDevServer,
node: ModuleNode,
): Promise<string | undefined> {
// For ALL CSS files (including CSS modules), get the transformed content
// and extract __vite__css. Vite's transform puts the final CSS (with hashed
// class names for modules) into the __vite__css variable.
- const transformResult = await vite.transformRequest(node.url)
+ let transformResult: Awaited<ReturnType<ViteDevServer['transformRequest']>>
+ try {
+ transformResult = await vite.transformRequest(node.url)
+ } catch {
+ return undefined
+ }
if (!transformResult?.code) return undefined
// Extract CSS content from Vite's transformed module
return extractCssFromViteModule(transformResult.code)
}🤖 Prompt for AI Agents
In @packages/start-plugin-core/src/dev-server-plugin/dev-styles.ts around lines
146 - 158, The loadCssContent function currently calls
vite.transformRequest(node.url) which can throw and break the entire styles
traversal; wrap that call in a try/catch inside loadCssContent so any thrown
error is caught, optionally log the error (or debug) and return undefined to
make CSS loading best-effort, and keep the existing check for
transformResult?.code before calling extractCssFromViteModule; ensure no
transformRequest exceptions bubble out of loadCssContent.
| // Middleware to serve collected CSS for dev mode | ||
| // Security: Route IDs from query params are validated against TSS_ROUTES_MANIFEST. | ||
| // Only routes that exist in the manifest will have their CSS collected. | ||
| // Arbitrary file paths cannot be injected. | ||
| viteDevServer.middlewares.use(async (req, res, next) => { | ||
| const url = req.url ?? '' | ||
| if (!url.startsWith('/@tanstack-start/styles.css')) { | ||
| return next() | ||
| } | ||
|
|
||
| // Parse route IDs from query param | ||
| const urlObj = new URL(url, 'http://localhost') | ||
| const routesParam = urlObj.searchParams.get('routes') | ||
| const routeIds = routesParam ? routesParam.split(',') : [] | ||
|
|
||
| // Build entries list from route file paths | ||
| const entries: Array<string> = [] | ||
|
|
||
| // Look up route file paths from manifest | ||
| // Only routes registered in the manifest are used - this prevents path injection | ||
| const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as | ||
| | Record<string, { filePath: string; children?: Array<string> }> | ||
| | undefined | ||
|
|
||
| if (routesManifest && routeIds.length > 0) { | ||
| for (const routeId of routeIds) { | ||
| const route = routesManifest[routeId] | ||
| if (route?.filePath) { | ||
| entries.push(route.filePath) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const css = | ||
| entries.length > 0 | ||
| ? await collectDevStyles({ | ||
| viteDevServer, | ||
| entries, | ||
| }) | ||
| : undefined | ||
|
|
||
| res.setHeader('Content-Type', 'text/css') | ||
| res.setHeader('Cache-Control', 'no-store') | ||
| res.end(css ?? '') | ||
| }) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden the dev-styles middleware: strict path match + error handling for async failures.
startsWith('/@tanstack-start/styles.css')will also match e.g.styles.css.maporstyles.css/anything.new URL(...)andcollectDevStyles(...)can throw; right now that can surface as an unhandled async middleware error.
Proposed fix
viteDevServer.middlewares.use(async (req, res, next) => {
const url = req.url ?? ''
- if (!url.startsWith('/@tanstack-start/styles.css')) {
- return next()
- }
-
- // Parse route IDs from query param
- const urlObj = new URL(url, 'http://localhost')
- const routesParam = urlObj.searchParams.get('routes')
- const routeIds = routesParam ? routesParam.split(',') : []
+ let urlObj: URL
+ try {
+ urlObj = new URL(url, 'http://localhost')
+ } catch (err) {
+ return next(err as any)
+ }
+
+ if (urlObj.pathname !== '/@tanstack-start/styles.css') {
+ return next()
+ }
+
+ const routesParam = urlObj.searchParams.get('routes')
+ const routeIds = routesParam ? routesParam.split(',') : []
// Build entries list from route file paths
const entries: Array<string> = []
// Look up route file paths from manifest
// Only routes registered in the manifest are used - this prevents path injection
const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
| Record<string, { filePath: string; children?: Array<string> }>
| undefined
if (routesManifest && routeIds.length > 0) {
for (const routeId of routeIds) {
const route = routesManifest[routeId]
if (route?.filePath) {
entries.push(route.filePath)
}
}
}
- const css =
- entries.length > 0
- ? await collectDevStyles({
- viteDevServer,
- entries,
- })
- : undefined
+ let css: string | undefined
+ try {
+ css =
+ entries.length > 0
+ ? await collectDevStyles({ viteDevServer, entries })
+ : undefined
+ } catch (err) {
+ return next(err as any)
+ }
res.setHeader('Content-Type', 'text/css')
res.setHeader('Cache-Control', 'no-store')
res.end(css ?? '')
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Middleware to serve collected CSS for dev mode | |
| // Security: Route IDs from query params are validated against TSS_ROUTES_MANIFEST. | |
| // Only routes that exist in the manifest will have their CSS collected. | |
| // Arbitrary file paths cannot be injected. | |
| viteDevServer.middlewares.use(async (req, res, next) => { | |
| const url = req.url ?? '' | |
| if (!url.startsWith('/@tanstack-start/styles.css')) { | |
| return next() | |
| } | |
| // Parse route IDs from query param | |
| const urlObj = new URL(url, 'http://localhost') | |
| const routesParam = urlObj.searchParams.get('routes') | |
| const routeIds = routesParam ? routesParam.split(',') : [] | |
| // Build entries list from route file paths | |
| const entries: Array<string> = [] | |
| // Look up route file paths from manifest | |
| // Only routes registered in the manifest are used - this prevents path injection | |
| const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as | |
| | Record<string, { filePath: string; children?: Array<string> }> | |
| | undefined | |
| if (routesManifest && routeIds.length > 0) { | |
| for (const routeId of routeIds) { | |
| const route = routesManifest[routeId] | |
| if (route?.filePath) { | |
| entries.push(route.filePath) | |
| } | |
| } | |
| } | |
| const css = | |
| entries.length > 0 | |
| ? await collectDevStyles({ | |
| viteDevServer, | |
| entries, | |
| }) | |
| : undefined | |
| res.setHeader('Content-Type', 'text/css') | |
| res.setHeader('Cache-Control', 'no-store') | |
| res.end(css ?? '') | |
| }) | |
| viteDevServer.middlewares.use(async (req, res, next) => { | |
| const url = req.url ?? '' | |
| let urlObj: URL | |
| try { | |
| urlObj = new URL(url, 'http://localhost') | |
| } catch (err) { | |
| return next(err as any) | |
| } | |
| if (urlObj.pathname !== '/@tanstack-start/styles.css') { | |
| return next() | |
| } | |
| // Parse route IDs from query param | |
| const routesParam = urlObj.searchParams.get('routes') | |
| const routeIds = routesParam ? routesParam.split(',') : [] | |
| // Build entries list from route file paths | |
| const entries: Array<string> = [] | |
| // Look up route file paths from manifest | |
| // Only routes registered in the manifest are used - this prevents path injection | |
| const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as | |
| | Record<string, { filePath: string; children?: Array<string> }> | |
| | undefined | |
| if (routesManifest && routeIds.length > 0) { | |
| for (const routeId of routeIds) { | |
| const route = routesManifest[routeId] | |
| if (route?.filePath) { | |
| entries.push(route.filePath) | |
| } | |
| } | |
| } | |
| let css: string | undefined | |
| try { | |
| css = | |
| entries.length > 0 | |
| ? await collectDevStyles({ viteDevServer, entries }) | |
| : undefined | |
| } catch (err) { | |
| return next(err as any) | |
| } | |
| res.setHeader('Content-Type', 'text/css') | |
| res.setHeader('Cache-Control', 'no-store') | |
| res.end(css ?? '') | |
| }) |
🤖 Prompt for AI Agents
In @packages/start-plugin-core/src/dev-server-plugin/plugin.ts around lines 79 -
124, The middleware should only match the exact styles route and handle async
errors: replace the loose startsWith check with an exact pathname check (e.g.,
ensure new URL(req.url, base).pathname === '/@tanstack-start/styles.css' and
optionally verify req.method === 'GET'), and wrap the URL parsing +
collectDevStyles call in a try/catch so exceptions don't become unhandled; on
error log via viteDevServer.config.logger.error (or console.error) and respond
with a 500 status and an empty body. Keep the existing route manifest lookup
(TSS_ROUTES_MANIFEST) and entries/collectDevStyles usage, but move the new
URL(...) and await collectDevStyles(...) inside the try block and only call
res.setHeader/res.end in the normal path (use res.statusCode = 500; res.end('')
in the catch).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/react-router/tests/Scripts.test.tsx (1)
220-220: Add test coverage for production mode.The test expectation now includes the dev-styles link, but there's no corresponding test to verify this link is NOT rendered in production mode. Since this is a dev-only feature per the PR objectives, you should add a test that sets
process.env.NODE_ENV = 'production'(or the equivalent mechanism) and verifies the dev-styles link is absent from the rendered HTML.🧪 Suggested test to verify production behavior
Add a new test after this one:
test('dev-styles link is not rendered in production', async () => { const originalEnv = process.env.NODE_ENV process.env.NODE_ENV = 'production' const rootRoute = createRootRoute({ head: () => ({ meta: [{ title: 'Root' }], }), component: () => <HeadContent />, }) const indexRoute = createRoute({ path: '/', getParentRoute: () => rootRoute, }) const router = createRouter({ history: createMemoryHistory({ initialEntries: ['/'], }), routeTree: rootRoute.addChildren([indexRoute]), isServer: true, }) await router.load() const html = ReactDOMServer.renderToString( <RouterProvider router={router} />, ) expect(html).not.toContain('data-tanstack-start-dev-styles') expect(html).not.toContain('/@tanstack-start/styles.css') process.env.NODE_ENV = originalEnv })e2e/vue-start/css-modules/src/routes/modules.tsx (1)
9-28: Add explicit return type annotation for better type safety.The Modules component implementation is correct and follows Vue 3 JSX conventions. However, per the coding guidelines requiring TypeScript strict mode with extensive type safety, consider adding an explicit return type annotation.
♻️ Proposed enhancement for type safety
-function Modules() { +function Modules(): JSX.Element { return ( <div> <h1>CSS Collection Test - CSS Modules</h1>As per coding guidelines, TypeScript strict mode with extensive type safety should be used for all
**/*.{ts,tsx}files.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
e2e/react-start/css-modules/src/routes/modules.tsxe2e/solid-start/css-modules/src/routes/modules.tsxe2e/vue-start/css-modules/src/routes/modules.tsxpackages/react-router/tests/Scripts.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- e2e/react-start/css-modules/src/routes/modules.tsx
- e2e/solid-start/css-modules/src/routes/modules.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety for all code
Files:
packages/react-router/tests/Scripts.test.tsxe2e/vue-start/css-modules/src/routes/modules.tsx
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Implement ESLint rules for router best practices using the ESLint plugin router
Files:
packages/react-router/tests/Scripts.test.tsxe2e/vue-start/css-modules/src/routes/modules.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
Applied to files:
packages/react-router/tests/Scripts.test.tsxe2e/vue-start/css-modules/src/routes/modules.tsx
📚 Learning: 2025-12-25T13:04:55.492Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 6215
File: e2e/react-start/custom-basepath/package.json:13-17
Timestamp: 2025-12-25T13:04:55.492Z
Learning: In the TanStack Router repository, e2e test scripts are specifically designed to run in CI (which uses a Unix environment), so Unix-specific commands (like `rm -rf`, `&` for backgrounding, and direct environment variable assignments without `cross-env`) are acceptable in e2e test npm scripts.
Applied to files:
packages/react-router/tests/Scripts.test.tsx
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.
Applied to files:
packages/react-router/tests/Scripts.test.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router
Applied to files:
e2e/vue-start/css-modules/src/routes/modules.tsx
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.
Applied to files:
e2e/vue-start/css-modules/src/routes/modules.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions
Applied to files:
e2e/vue-start/css-modules/src/routes/modules.tsx
🧬 Code graph analysis (1)
e2e/vue-start/css-modules/src/routes/modules.tsx (3)
e2e/react-start/css-modules/src/routes/modules.tsx (1)
Route(5-7)e2e/solid-start/css-modules/src/routes/modules.tsx (1)
Route(5-7)e2e/vue-start/css-modules/src/routes/__root.tsx (1)
Route(11-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Test
- GitHub Check: Preview
🔇 Additional comments (2)
e2e/vue-start/css-modules/src/routes/modules.tsx (2)
2-7: LGTM!The imports and route definition follow the correct file-based routing pattern and are consistent with the React and Solid implementations.
1-1: Vite client reference correctly placed and consistent across framework implementations.The triple-slash directive is properly positioned on line 1 and matches the implementation in the React and Solid versions, enabling CSS Modules collection in development mode.
dgreif
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for getting this added! It will make a huge difference in DX. Just pointing out a few small bumps I ran into when trying it out
| }, []) | ||
|
|
||
| // Build the same href on both server and client for hydration match | ||
| const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't appear to use the vite base path if one is set, leading to a 404 when trying to fetch the styles
| if ( | ||
| !isRunnableDevEnvironment(serverEnv) || | ||
| // do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks) | ||
| 'dispatchFetch' in serverEnv | ||
| ) { | ||
| return | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this return gets hit if I have the nitro plugin running as well, causing the new middleware to not get registered, leading to a 404 for /@tanstack-start/styles.css

fixes #3023
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.